package com.study.chunguard.common.exception;

import cn.dev33.satoken.exception.*;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.extra.servlet.ServletUtil;
import com.study.chunguard.common.exception.code.ResponseCode;
import com.study.chunguard.common.exception.model.ApiErrorLog;
import com.study.chunguard.common.model.Result;
import com.study.chunguard.common.utils.json.JsonUtils;
import com.study.chunguard.common.utils.servlet.ServletUtils;
import global.namespace.truelicense.api.LicenseManagementException;
import global.namespace.truelicense.api.UncheckedLicenseManagementException;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.util.Assert;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import java.sql.SQLException;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;

import static com.study.chunguard.common.exception.code.ResponseCode.SYSTEM_LICENSE_ERROR;

/**
 * 异常通用处理
 *
 * @author zhaowenhao
 * @since 2023-03-25
 */
@ResponseBody
@Slf4j
@SuppressWarnings("java:S1192")
public abstract class DefaultExceptionAdvice {

    /**
     * IllegalArgumentException异常处理返回json
     */
    @ExceptionHandler({IllegalArgumentException.class})
    public Result<?> badRequestException(IllegalArgumentException e) {
        return doHandle("参数解析失败", e);
    }

    /**
     * AccessDeniedException异常处理返回json
     */
    @ExceptionHandler({AccessDeniedException.class})
    public Result<?> badMethodExpressException(AccessDeniedException e) {
        return doHandle("没有权限请求当前方法", e);
    }

    /**
     *
     */
    @ExceptionHandler({HttpRequestMethodNotSupportedException.class})
    public Result<?> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
        return doHandle("不支持当前请求方法", e);
    }

    /**
     *
     */
    @ExceptionHandler({HttpMediaTypeNotSupportedException.class})
    public Result<?> handleHttpMediaTypeNotSupportedException(HttpMediaTypeNotSupportedException e) {
        return doHandle("不支持当前媒体类型", e);
    }

    /**
     * SQLException sql异常处理
     */
    @ExceptionHandler({SQLException.class})
    public Result<?> handleSqlException(SQLException e) {
        return doHandle("服务异常", e, true);
    }

    /**
     * BusinessException 业务异常处理
     */
    @ExceptionHandler(BusinessException.class)
    public Result<?> handleException(BusinessException e) {
        log.error("业务异常", e);
        return Result.fail(e.getCode(), e.getMessage());
    }

    /**
     * LicenseManagementException 许可证异常，指示在生成、安装、加载、验证或卸载许可证密钥时出现的问题。
     */
    @ExceptionHandler(LicenseManagementException.class)
    public Result<?> handleException(LicenseManagementException e) {
        log.error("许可证异常", e);
        return Result.fail(SYSTEM_LICENSE_ERROR.getCode(), SYSTEM_LICENSE_ERROR.getMsg() + ": " + e.getLocalizedMessage());
    }


    /**
     * UncheckedLicenseManagementException 许可证运行时异常，指示在生成、安装、加载、验证或卸载许可证密钥时出现的问题。
     */
    @ExceptionHandler(UncheckedLicenseManagementException.class)
    public Result<?> handleException(UncheckedLicenseManagementException e) {
        log.error("许可证异常", e);
        return Result.fail(SYSTEM_LICENSE_ERROR.getCode(), SYSTEM_LICENSE_ERROR.getMsg() + ": " + e.getLocalizedMessage().replaceAll("global.namespace.truelicense.*Exception: ", ""));
    }

    /**
     * ValidationFailedException 参数校验异常
     */
    @ExceptionHandler(ValidationFailedException.class)
    public Result<?> handleValidationFailedException(ValidationFailedException e) {
        log.error("参数校验异常", e);
        return Result.fail(e.getMessage());
    }

    /**
     * IdempotencyException 幂等性异常
     */
    @ExceptionHandler(IdempotencyException.class)
    public Result<?> handleException(IdempotencyException e) {
        log.error("幂等性异常", e);
        return doHandle(e.getMessage(), e);
    }

    /**
     * 参数校验异常
     *
     * @param e MethodArgumentNotValidException
     * @return Result
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<?> handlerException(MethodArgumentNotValidException e) {
        log.error("参数校验异常", e);
        List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
        String fieldErrorMsg = getFieldErrorMsg(fieldErrors);
        return doHandle(fieldErrorMsg, e);
    }

    /**
     * 参数校验异常
     *
     * @param e BindException
     * @return Result
     */
    @ExceptionHandler(BindException.class)
    public Result<?> handlerException(BindException e) {
        log.error("参数校验异常", e);
        List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
        String fieldErrorMsg = getFieldErrorMsg(fieldErrors);
        return doHandle(fieldErrorMsg, e);
    }

    /**
     * 认证异常
     *
     * @param e AuthenticationException
     * @return Result
     */
    @ExceptionHandler(AuthenticationException.class)
    public Result<?> handlerException(AuthenticationException e) {
        log.error("认证异常", e);
        return doHandle(e.getMessage(), e);
    }

    // region sa-token 异常处理

    @ExceptionHandler(value = {NotLoginException.class})
    public Result<?> handle(NotLoginException e) {
        log.error("NotLoginException:{}", e.getMessage());
        return Result.fail(ResponseCode.NO_SESSION);
    }

    @ExceptionHandler(value = {NotPermissionException.class})
    public Result<?> handle(NotPermissionException e) {
        log.error("NotPermissionException:{}", e.getMessage());
        return Result.fail(ResponseCode.ACCESS_UNAUTHORIZED);
    }

    @ExceptionHandler(value = {NotRoleException.class})
    public Result<?> handle(NotRoleException e) {
        log.error("NotRoleException:{}", e.getMessage());
        return Result.fail(ResponseCode.ACCESS_UNAUTHORIZED);
    }

    @ExceptionHandler(value = {NotSafeException.class})
    public Result<?> handle(NotSafeException e) {
        log.error("NotSafeException:{}", e.getMessage());
        return Result.fail(ResponseCode.ACCESS_UNAUTHORIZED);
    }

    @ExceptionHandler(value = {SameTokenInvalidException.class})
    public Result<?> handle(SameTokenInvalidException e) {
        log.error("SameTokenInvalidException:{}", e.getMessage());
        return Result.fail(ResponseCode.TOKEN_INVALID);
    }

    /**
     * 处理Sa-Token的异常
     *
     * @param e Sa-Token异常
     * @return Result
     */
    @ExceptionHandler(value = {SaTokenException.class})
    public Result<?> handle(SaTokenException e) {
        log.error("认证异常", e);
        return doHandle(e.getMessage(), e);
    }
    // endregion sa-token 异常处理

    /**
     * 所有未知异常统一处理
     */
    @ExceptionHandler(Exception.class)
    public Result<?> handleException(HttpServletRequest req, Exception ex) {
        log.error("[defaultExceptionHandler]", ex);
        // 插入异常日志
        this.createExceptionLog(req, ex);
        // 返回 ERROR Result
        return doHandle("系统繁忙", ex, true);
    }

    /**
     * 处理异常,默认不打印异常堆栈,并返回前端异常信息
     *
     * @param msg 返给前端的异常信息
     * @param e   异常
     * @return 返回前端的响应体
     */
    private Result<?> doHandle(String msg, Exception e) {
        return doHandle(msg, e, false);
    }

    /**
     * 处理异常，并返回前端异常信息
     *
     * @param msg               返给前端的异常信息
     * @param e                 异常
     * @param ifPrintStackTrace 是否打印异常堆栈,系统级或未知异常建议打印
     * @return 返回前端的响应体
     */
    @SuppressWarnings("CallToPrintStackTrace")
    private Result<?> doHandle(String msg, Exception e, boolean ifPrintStackTrace) {
        if (ifPrintStackTrace) {
            e.printStackTrace();
        }
        log.error(msg, e);
        return Result.fail(msg);
    }

    /**
     * 获取参数校验错误信息
     *
     * @param fieldErrors fieldErrors
     * @return 参数校验错误信息
     */
    private String getFieldErrorMsg(List<FieldError> fieldErrors) {
        StringBuilder errorMsg = new StringBuilder("参数校验失败：");
        fieldErrors.forEach(fieldError -> errorMsg.append(fieldError.getField()).append("-").append(fieldError.getDefaultMessage()).append(";"));
        return errorMsg.toString();
    }

    @SuppressWarnings("java:S1181")
    private void createExceptionLog(HttpServletRequest req, Throwable e) {
        // 插入错误日志
        ApiErrorLog errorLog = new ApiErrorLog();
        try {
            // 初始化 errorLog
            initExceptionLog(errorLog, req, e);
            // 执行插入 errorLog
            appendErrorLog(errorLog);
        } catch (Throwable th) {
            log.error("[createExceptionLog][url({}) log({}) 发生异常]", req.getRequestURI(), JsonUtils.toJsonString(errorLog), th);
        }
    }

    /**
     * 插入错误日志, 子类实现
     *
     * @param errorLog 错误日志
     */
    protected void appendErrorLog(ApiErrorLog errorLog) {
        // 插入错误日志,默认不做任何事情
    }

    @SuppressWarnings("VulnerableCodeUsages")
    private void initExceptionLog(ApiErrorLog errorLog, HttpServletRequest request, Throwable e) {
        // 设置异常字段
        errorLog.setExceptionName(e.getClass().getName());
        errorLog.setExceptionMessage(ExceptionUtil.getMessage(e));
        errorLog.setExceptionRootCauseMessage(ExceptionUtil.getRootCauseMessage(e));
        errorLog.setExceptionStackTrace(ExceptionUtil.stacktraceToString(e));
        StackTraceElement[] stackTraceElements = e.getStackTrace();
        Assert.notEmpty(stackTraceElements, "异常 stackTraceElements 不能为空");
        StackTraceElement stackTraceElement = stackTraceElements[0];
        errorLog.setExceptionClassName(stackTraceElement.getClassName());
        errorLog.setExceptionFileName(stackTraceElement.getFileName());
        errorLog.setExceptionMethodName(stackTraceElement.getMethodName());
        errorLog.setExceptionLineNumber(stackTraceElement.getLineNumber());
        // 设置其它字段
        errorLog.setTraceId(MDC.get("traceId"));
        errorLog.setRequestUrl(request.getRequestURI());
        Map<String, Object> requestParams = MapUtil.<String, Object>builder()
                .put("query", ServletUtil.getParamMap(request))
                .put("body", ServletUtil.getBody(request)).build();
        errorLog.setRequestParams(JsonUtils.toJsonString(requestParams));
        errorLog.setRequestMethod(request.getMethod());
        errorLog.setUserAgent(ServletUtils.getUserAgent(request));
        errorLog.setUserIp(ServletUtil.getClientIP(request));
        errorLog.setExceptionTime(LocalDateTime.now());
    }
}
